<?php
/**
 * In this file the class '\BF\Translator' is defined.
 *
 * @author     Ulf Kadner <ulfikado@gmail.com>
 * @category   Beluga_Framework
 * @copyright  (c) 2014, Ulf Kadner
 * @package    Core
 * @since      2014-10-25
 * @version    1.0.0
 */

namespace BF
{

   /**
    * This class defines a object, to access some translation data in a predefined format.
    *
    * The class works as a singleton like follow:
    *
    * You can define one or more folders that contain some localized translation data (translation folder)
    * The first folder is defined automatic and is used by Framework for internal translations. Dont remove it or
    * the framework does not use localized data.
    *
    * Every translation folder must following a predefined structure:
    *
    * <pre>
    * [translation folder]
    *   |
    *   +- [de_DE]
    *   |    |
    *   |    +- namespace1.php
    *   |    +- namespace2.php
    *   |    +- …
    *   |
    *   +- [nl]
    *   |    |
    *   |    +- namespace1.php
    *   |    +- namespace2.php
    *   |    +- …
    *   +- […]
    * </pre>
    *
    * You have to define for every loacale a separate folder. If there is no need to specify translations for a
    * specific country/region you can only use the LID (Language ID) als folder name.
    *
    * For every locale you can define one or more namespaces as separate PHP files wich contains the translation for
    * a specific application range. If you use more namespace it reduces more the overhead of loaded data, because
    * the translation data are only loaded if they are needed.
    *
    * The format of the namespace files is:
    *
    * <code>
    * <?php
    *
    * return [
    *
    *     'Origin string…' => 'Originale Zeichenkette…',
    *     'Its me!'        => 'Das bin ich!'
    *     // more...
    *
    * ];
    *
    * ?>
    * </code>
    *
    * So all translation data must be returned as a associative Array.
    *
    * But have attention with the translation namespaces! Every namespace can be used only one time for whole
    * application! The framework internal namespace all starts with a single underscore '_'.
    *
    * For easier use the functions \BF\_(), \BF\_0, \BF\_1, \BF\_2, \BF\_3, \BF\_4 are defined.
    *
    * A short example usage
    *
    * <code>
    * <?php
    *
    * // First you have to configure out the Locale if there is a need for it.
    * // After its done you have to define your translation folder
    *
    * \BF\Translator::GetInstance()->addTranslationFolder( '/foo/bar/baz' );
    * // Its the same with the function
    * #\BF\add_translation_folder( '/foo/bar/baz' );
    * // And also with the \BF\FrameworkConfig
    * #\BF\FrameworkConfig::GetInstance()->Translator->addTranslationFolder( '/foo/bar/baz' );
    *
    * // If you need a translated string in you application you can access it by
    *
    * $translated = \BF\_( 'Namespace', 'Im a string to translate :-)' );
    *
    * // If the defined namespace is not loaded it will be loaded automaticly.
    *
    * // If a translated string should contains some placeholders in (s)printf style the \BF\_1-4 functions can
    * // used.
    *
    * $translated = \BF\_1( 'Namespace', 'Let me give you %d eggs.', 20 );
    *
    * // or:
    *
    * $translated = \BF\_2( 'Namespace',
     *     'Let me give you %d eggs and %s bred.', 20, 'some' );
     *
     * // or:
     *
     * $translated = \BF\_3( 'Namespace',
     *     'Let me give you %d eggs and %s bred with %d gram buther.',
     *     20, 'some', 5 );
     *
     * </code>
     *
     * @since  v0.1
    */
   final class Translator
   {

      # <editor-fold defaultstate="collapsed" desc=" - - -   P R I V A T E   F I E L D S   - - - - - - - - - - - - - - - - - - - - - -">

      /**
       * Array with all currently loaded translations of a language.
       *
       * @var    array
       */
      private $data;

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P R I V A T E   S T A T I C   F I E L D S   - - - - - - - - - - - - - - -">

      /**
       * Holds the singleton instance of this class.
       *
       * @var \BF\Translator
       */
      private static $instance = null;

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - >   P R I V A T E   C O N S T R U C T O R   - - - - - - - - - - - - - - - - -">

      /**
       * Private constructor because its a singleton.
       */
      private function __construct()
      {
         // Init the translation data array
         $this->data = array();
      }

      /**
       * Disable cloning, because its a singleton.
       */
      private function __clone() { }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P U B L I C   M E T H O D S   - - - - - - - - - - - - - - - - - - - - - -">

      /**
       * Returns the translation of the defined string for defined namespace.
       *
       * @param  string $namespace The translation namespace that defines the translation
       * @param  string $str       The string to translate
       * @return string            Returns the translated string, or $str if no translation exists.
       */
      public function get( $namespace, $str )
      {

         // If the translation is disabled return the origin untranslated string
         if ( ! \BF\FrameworkConfig::GetInstance()->Translator->Enabled )
         {
            return $str;
         }

         // If no data are loaded for defined namespace load it if defined.
         if ( ! isset( $this->data[ $namespace ] ) )
         {
            $this->loadAdd( $namespace );
         }

         // If undefined return $str unchanged
         if ( ! isset( $this->data[ $namespace ][ $str ] ) )
         {
            return $str;
         }

         // return the translation
         return $this->data[ $namespace ][ $str ];

      }

      /**
       * Removes all translation data from a instance. If $namespace is defined, only the translation data for
       * this namespace are removed.
       *
       * @param  string $namespace Optional namespace, if only data for it should be removed.
       */
      public function clear( $namespace = null )
      {

         if ( empty( $namespace ) )
         {
            // Claer the whol defined translation data
            $this->data = array();
         }
         else if ( isset ( $this->data[ $namespace ] ) )
         {
            // Clear the namespace translation data
            unset ( $this->data[ $namespace ] );
         }

      }

      /**
       * Adds a translation folder.
       *
       * The folder structure should looks like:
       *
       * <pre>
       * [translation folder]
       *   |
       *   +- [de_DE]
       *   |    |
       *   |    +- namespace1.php
       *   |    +- namespace2.php
       *   |    +- …
       *   |
       *   +- [nl]
       *   |    |
       *   |    +- namespace1.php
       *   |    +- namespace2.php
       *   |    +- …
       *   +- […]
       * </pre>
       *
       * @param  string $folder The folder that contains the namespace files with the translations.
       * @return boolean        TRUE on success, FALSE if $folder dont exists, or is allready registered
       */
      public function addTranslationFolder( $folder )
      {
         return \BF\FrameworkConfig::GetInstance()->Translator->addTranslationFolder( $folder );
      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P R I V A T E   M E T H O D S   - - - - - - - - - - - - - - - - - - - - -">

      /**
       * @since  v0.1
       */
      private function loadAdd( $namespace )
      {

         // Loop all defined source folders
         foreach ( \BF\FrameworkConfig::GetInstance()->Translator->TransFolders as $sf )
         {

            // Build the path for required locale LCID
            $lcf = \rtrim( $sf, '\\/' ) . \DIRECTORY_SEPARATOR . (string) \BF\Locale::GetInstance();

            // If the path not exists also check for a path for LID only
            if ( ! \is_dir( $lcf ) )
            {
               // Build the path for required locale LID
               $lcf = \rtrim( $sf, '\\/' ) . \DIRECTORY_SEPARATOR . \BF\Locale::GetInstance()->getLID();
               // if it also dont exists ignore the current source folder and goto next
               if ( ! \is_dir( $lcf ) )
               {
                  continue;
               }
            }

            // Build the path of the required PHP "namespace translation file"
            $lcff = $lcf . \DIRECTORY_SEPARATOR . $namespace . '.php';

            // If the file not exists ignore the current source folder and goto next
            if ( ! \file_exists( $lcff ) )
            {
               continue;
            }

            // Getting the translations from the fie
            /** @noinspection PhpIncludeInspection */
            $translations = include $lcff;

            // If no translations are defined ignore the current source folder and goto next
            if ( empty( $translations ) || ! \is_array( $translations ) )
            {
               continue;
            }

            // Assign the translation data and break the loop
            $this->data[ $namespace ] = $translations;
            break;
         }

         // Set a empty array if init dont gets some data
         if ( ! isset( $this->data[ $namespace ] ) )
         {
            $this->data[ $namespace ] = array();
         }

      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P U B L I C   S T A T I C   M E T H O D S   - - - - - - - - - - - - - - -">

      /**
       * Returns the Translator singleton instance and creates it by need.
       *
       * @return \BF\Translator
       * @since  v0.1
       */
      public static function GetInstance()
      {

         // Create a new instance by need
         if ( \is_null( self::$instance ) )
         {
            self::$instance = new \BF\Translator();
         }

         // return the singleton instance
         return self::$instance;

      }

      # </editor-fold>

   }

   # <editor-fold defaultstate="collapsed" desc="FUNCTIONS '_' + '_0' + '_1' + '_2' + '_3' + '_4'">

   /**
    * Returns the translation of the string for defined namespace. For translation the current Locale is used.
    *
    * @param  string $namespace The translation namespace.
    * @param  string $string The string to translate
    * @param  mixed  $var1   The first replacement (default=NULL if unused)
    * @param  mixed  $var2   The second replacement (default=NULL if unused)
    * @param  mixed  $var3   The third replacement (default=NULL if unused)
    * @param  mixed  $var4   The fourth replacement (default=NULL if unused)
    * @return string Translated string, or origin string if no tranlation exists.
    * @since  v0.1
    */
   function _( $namespace, $string, $var1 = null, $var2 = null, $var3 = null, $var4 = null )
   {
      if ( \is_null( $var1 ) )
      {
         return \BF\Translator::GetInstance()->get( $namespace, $string );
      }
      if ( \is_null( $var2 ) )
      {
         return \BF\_1( $namespace, $string, $var1 );
      }
      if ( \is_null( $var3 ) )
      {
         return \BF\_2( $namespace, $string, $var1, $var2 );
      }
      if ( \is_null( $var4 ) )
      {
         return \BF\_3( $namespace, $string, $var1, $var2, $var3 );
      }
      return \BF\_4( $namespace, $string, $var1, $var2, $var3, $var4 );
   }

   /**
    * Returns the translation of the string for defined namespace. For translation the current Locale is used.
    *
    * @param  string $namespace The translation namespace.
    * @param  string $string The string to translate
    * @return string Translated string, or origin string if no tranlation exists.
    * @since  v0.1
    */
   function _0( $namespace, $string )
   {
      return \BF\Translator::GetInstance()->get( $namespace, $string );
   }

   /**
    * Returns the translation of the string for defined namespace with the use of sprintf.
    * For translation the current Locale is used.
    *
    * @param  string $namespace The translation namespace.
    * @param  string $string The string to translate
    * @param  mixed  $var The first replacement
    * @return string Translated string, or origin string if no tranlation exists.
    * @since  v0.1
    */
   function _1( $namespace, $string, $var )
   {
      try
      {
         return \sprintf( \BF\Translator::GetInstance()->get( $namespace, $string ), $var );
      }
      catch ( \BF\PhpException $ex )
      {
         return \BF\Translator::GetInstance()->get( $namespace, $string );
      }
   }

   /**
    * Returns the translation of the string for defined namespace with the use of sprintf.
    * For translation the current Locale is used.
    *
    * @param  string $namespace The translation namespace.
    * @param  string $string The string to translate
    * @param  mixed $var1 The first replacement
    * @param  mixed $var2 The second replacement
    * @return string Translated string, or origin string if no tranlation exists.
    * @since  v0.1
    */
   function _2( $namespace, $string, $var1, $var2 )
   {
      try
      {
         return \sprintf( \BF\Translator::GetInstance()->get( $namespace, $string ), $var1, $var2 );
      }
      catch ( \BF\PhpException $ex )
      {
         return \BF\Translator::GetInstance()->get( $namespace, $string );
      }
   }

   /**
    * Returns the translation of the string for defined namespace with the use of sprintf.
    * For translation the current Locale is used.
    *
    * @param  string $namespace The translation namespace.
    * @param  string $string The string to translate
    * @param  mixed $var1 The first replacement
    * @param  mixed $var2 The second replacement
    * @param  mixed $var3 The third replacement
    * @return string Translated string, or origin string if no tranlation exists.
    * @since  v0.1
    */
   function _3( $namespace, $string, $var1, $var2, $var3 )
   {
      try
      {
         return \sprintf( \BF\Translator::GetInstance()->get( $namespace, $string ), $var1, $var2, $var3 );
      }
      catch ( \BF\PhpException $ex )
      {
         return \BF\Translator::GetInstance()->get( $namespace, $string );
      }
   }

   /**
    * Returns the translation of the string for defined namespace with the use of sprintf.
    * For translation the current Locale is used.
    *
    * @param  string $namespace The translation namespace.
    * @param  string $string The string to translate
    * @param  mixed $var1 The first replacement
    * @param  mixed $var2 The second replacement
    * @param  mixed $var3 The third replacement
    * @param  mixed $var4 The fourth replacement
    * @return string Translated string, or origin string if no tranlation exists.
    * @since  v0.1
    */
   function _4( $namespace, $string, $var1, $var2, $var3, $var4 )
   {
      try
      {
         return \sprintf( \BF\Translator::GetInstance()->get( $namespace, $string ), $var1, $var2, $var3, $var4 );
      }
      catch ( \BF\PhpException $ex )
      {
         return \BF\Translator::GetInstance()->get( $namespace, $string );
      }
   }

   # </editor-fold>

   # <editor-fold defaultstate="collapsed" desc="FUNCTION 'add_translation_folder'">

   /**
    * Adds a translation folder.
    *
    * The folder structure should looks like:
    *
    * <pre>
    * [translation folder]
    *   |
    *   +- [de_DE]
    *   |    |
    *   |    +- namespace1.php
    *   |    +- namespace2.php
    *   |    +- …
    *   |
    *   +- [nl]
    *   |    |
    *   |    +- namespace1.php
    *   |    +- namespace2.php
    *   |    +- …
    *   +- […]
    * </pre>
    *
    * @param  string $folder
    * @return boolean TRUE on success, FALSE if $folder dont exists, or is allready registered
    * @since  v0.1
    */
   function add_translation_folder( $folder )
   {
      return \BF\FrameworkConfig::GetInstance()->Translator->addTranslationFolder( $folder );
   }

   # </editor-fold>

}

